<template>
  <demo-content :title="title">
    <p>
      HTML5 的 Canvas 是一种立即模式绘图系统。当向 canvas 中绘制内容时,
      浏览器立即就会将这些内容画到 canvas 之中，然后立即忘掉刚才所画的东西。
      如果要实现的是一个绘画应用程序的话，那么立即模式绘图系统很合适。
      但如果要实现的是那种可以让用户创建并操作绘图对象的画图应用程序,
      那么最好还是将编辑并绘制的哪些图形对象保存到一份列表之中。
    </p>
    <div class="tools">
      <stroke-color-selector v-model:value="strokeColor" />
      <fill-color-selector v-model:value="fillColor" />
      <div>
        <span>Sides: </span>
        <select v-model="sides">
          <option v-for="v of [4, 6, 7, 10, 12, 20]" :value="v">{{ v }}</option>
        </select>
      </div>
      <div>
        <span>Start angle:</span>
        <select v-model="startAngle">
          <option v-for="i of [0, 22.5, 45, 67.5, 90]" :value="i">
            {{ i }}
          </option>
        </select>
      </div>
      <div>
        <span>Fill</span>
        <input type="checkbox" v-model="fill" />
      </div>
      <div>
        <button
          @click="
            () => {
              initCtx();
              polygons.length = 0;
            }
          "
        >
          Erase all
        </button>
      </div>
    </div>
    <div>编辑模式: <input type="checkbox" v-model="edit" /></div>
    <canvas
      :style="{ cursor: edit ? 'pointer' : 'crosshair' }"
      ref="canvasRef"
      width="800"
      height="600"
    ></canvas>
  </demo-content>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";
import { StrokeColorSelector, FillColorSelector } from "./components";
import { DrawingAssist, Point, drawGrid, rect } from "./utils";
import { watchEffect } from "vue";

const title = "多边形对象";
// 指定组件上的属性
defineOptions({ title });
const strokeColor = ref("cornflowerblue");
const fillColor = ref("#ff000088");
const sides = ref(4);
const startAngle = ref(22.5);
const fill = ref(true);
const edit = ref(false);

const dragInfo: { target: Polygon | null; originPos: Point | null } = {
  target: null,
  originPos: null,
};

const canvasRef = ref<HTMLCanvasElement>();
let ctx: CanvasRenderingContext2D;
const polygons: Polygon[] = [];
let currentPolygon: Polygon | null = null;
onMounted(() => {
  const canvas = canvasRef.value!;
  ctx = canvas.getContext("2d")!;
  const drawPolygons = () => {
    for (const polygon of polygons) {
      polygon.stroke(ctx);
      polygon.fill(ctx);
    }
  };
  initCtx();
  const drawingAssist = DrawingAssist.create(ctx)
    .setDrawingCallee((ctx, startPos, endPos) => {
      if (edit.value) {
        if (dragInfo.target) {
          dragInfo.target.x = dragInfo.originPos!.x + endPos.x - startPos.x;
          dragInfo.target.y = dragInfo.originPos!.y + endPos.y - startPos.y;
        }
        drawPolygons();
        return;
      }
      const { x: x1, y: y1 } = startPos;
      const { x: x2, y: y2 } = endPos;
      const polygonData: PolygonData = {
        ...startPos,
        radius: Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2),
        sides: sides.value,
        startAngle: startAngle.value,
        strokeStyle: strokeColor.value,
        fillStyle: fillColor.value,
        filled: fill.value,
      };
      currentPolygon = new Polygon(polygonData);
      currentPolygon.stroke(ctx);
      currentPolygon.fill(ctx);
    })
    .setFinishCallee(() => {
      if (currentPolygon) {
        polygons.push(currentPolygon);
        currentPolygon = null;
      }
      dragInfo.target = null;
    });
  watchEffect(() => {
    if (edit.value) {
      drawingAssist.disuseGuidewires();
      drawingAssist.setStartCallee((ctx, p) => {
        dragInfo.target = dragInfo.originPos = null;
        for (const polygon of polygons) {
          polygon.createPath(ctx);
          if (ctx.isPointInPath(p.x, p.y)) {
            dragInfo.target = polygon;
            dragInfo.originPos = { x: polygon.x, y: polygon.y };
          }
        }

        initCtx();
        drawingAssist.takeSnapshot();
        drawPolygons();
      }, false);
    } else {
      drawingAssist.setStartCallee(null);
      drawingAssist.useGuidewires({ type: "StartPos" });
    }
  });
});

function initCtx() {
  ctx.beginPath();
  rect.clear(ctx);
  drawGrid(ctx, { stepx: 10, stepy: 10 });
}

interface PolygonData {
  x: number;
  y: number;
  radius: number;
  sides: number;
  startAngle: number;
  strokeStyle: string;
  fillStyle: string;
  filled: boolean;
}

// 定义一个 Polygon 对象
class Polygon {
  constructor(private _data: PolygonData) {}
  getPoints() {
    const points: Point[] = [];
    const { sides, startAngle, x, y, radius: r } = this._data;
    const radian0 = (startAngle / 180) * Math.PI;
    const deltaRadian = (2 * Math.PI) / sides;
    for (let i = 0; i < sides; ++i) {
      const radian = radian0 + i * deltaRadian;
      points.push({ x: x + r * Math.cos(radian), y: y + r * Math.sin(radian) });
    }
    return points;
  }
  get x() {
    return this._data.x;
  }
  set x(v: number) {
    this._data.x = v;
  }
  get y() {
    return this._data.y;
  }
  set y(v: number) {
    this._data.y = v;
  }
  createPath(ctx: CanvasRenderingContext2D) {
    const points = this.getPoints();
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);
    for (let i = 1; i < points.length; ++i) {
      ctx.lineTo(points[i].x, points[i].y);
    }
    ctx.closePath();
  }
  stroke(ctx: CanvasRenderingContext2D) {
    ctx.save();
    this.createPath(ctx);
    ctx.strokeStyle = this._data.strokeStyle;
    ctx.stroke();
    ctx.restore();
  }
  fill(ctx: CanvasRenderingContext2D) {
    if (!this._data.filled) return;
    ctx.save();
    this.createPath(ctx);
    ctx.fillStyle = this._data.fillStyle;
    ctx.fill();
    ctx.restore();
  }
  move(x: number, y: number) {
    this._data.x = x;
    this._data.y = y;
  }
}
</script>

<style src="./default.css" scoped></style>
